package com.hy.video.monitor.vmanager.gb28181.play;

import com.alibaba.fastjson.JSONArray;
import com.hy.video.monitor.common.StreamInfo;
import com.hy.video.monitor.gb28181.bean.SsrcTransaction;
import com.hy.video.monitor.gb28181.session.VideoStreamSessionManager;
import com.hy.video.monitor.gb28181.bean.Device;
import com.hy.video.monitor.gb28181.transmit.callback.DeferredResultHolder;
import com.hy.video.monitor.gb28181.transmit.callback.RequestMessage;
import com.hy.video.monitor.media.zlm.ZLMRESTfulUtils;
import com.hy.video.monitor.media.zlm.dto.MediaServerItem;
import com.hy.video.monitor.service.IMediaServerService;
import com.hy.video.monitor.storager.IRedisCatchStorage;
import com.hy.video.monitor.vmanager.bean.VMCResult;
import com.hy.video.monitor.vmanager.gb28181.play.bean.PlayResult;
import com.hy.video.monitor.service.IMediaService;
import com.hy.video.monitor.service.IPlayService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSONObject;
import com.hy.video.monitor.gb28181.transmit.cmd.impl.SIPCommander;
import com.hy.video.monitor.storager.IVideoManagerStorager;
import org.springframework.web.context.request.async.DeferredResult;

import java.util.List;
import java.util.UUID;

@Api(tags = "国标设备点播")
@CrossOrigin
@RestController
@RequestMapping("/api/play")
public class PlayController {

    private final static Logger logger = LoggerFactory.getLogger(PlayController.class);

    @Autowired
    private SIPCommander cmder;

    @Autowired
    private VideoStreamSessionManager streamSession;

    @Autowired
    private IVideoManagerStorager storager;

    @Autowired
    private IRedisCatchStorage redisCatchStorage;

    @Autowired
    private ZLMRESTfulUtils zlmresTfulUtils;//主动调用ZLM restful api

    @Autowired
    private DeferredResultHolder resultHolder;//异步请求处理句柄

    @Autowired
    private IPlayService playService;//点播处理

    @Autowired
    private IMediaService mediaService;//媒体信息业务

    @Autowired
    private IMediaServerService mediaServerService;//流媒体服务业务



    /**
     * 通知设备上传媒体流,通知设备推流
     * zs-monitor 先发起呼叫流程得到了ssrc和stream id， 再返回url给前端播放， 同时摄像头会推流给zlm
     * DeferredResult:异步请求
     * 异步查找流：查找一个流，当这个流还没在线(主播未推流)，我们不能立即掐断播放器，因为这个主播在几秒内会推流上来，
     * 也就是我们可以先播放再推流，这样客户端不会等待推流完了再打开播放器，这样极大的提高了客户端的体验感。
     * <p>
     * 适用基于rtp 1对1推流的物联网开发，因为他很难确定推流上来的时机，这样使用异步就会弥补等待推流的时间。
     * <p>
     * 播放器、信令服务对接流媒体流程：
     * 1、zs-monitor 通过 gegSystemCfg 函数和 zlm 连接，握手
     * 2、前端请求镜头，会把国标号码发给zs-monitor, zs-monitor 可以根据国标号码找到对应的镜头，可以通过rtsp、或国标协议发命令给zlm。
     * zlm可以rtsp拉流，或者国标收流，把视频流接入zlm
     * 3、zlm 会返回对应镜头的flv等拉流地址url 给zs-monitor ,zs-monitor 的网页就可以播放视频了。
     *
     * @param deviceId
     * @param channelId
     * @return
     */
    @ApiOperation("开始点播")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
            @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class),
    })
    @GetMapping("/start/{deviceId}/{channelId}")
    public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId,
                                                       @PathVariable String channelId) {

        // 获取可用的zlm
        Device device = storager.queryVideoDevice(deviceId);
        MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
        PlayResult playResult = playService.play(newMediaServerItem, deviceId, channelId, null, null, null);

        return playResult.getResult();
    }

    @ApiOperation("停止点播")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
            @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class),
    })
    @GetMapping("/stop/{deviceId}/{channelId}")
    public DeferredResult<ResponseEntity<String>> playStop(@PathVariable String deviceId, @PathVariable String channelId) {

        logger.debug(String.format("设备预览/回放停止API调用，streamId：%s_%s", deviceId, channelId ));

        String uuid = UUID.randomUUID().toString();
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>();

        // 录像查询以channelId作为deviceId查询
        String key = DeferredResultHolder.CALLBACK_CMD_STOP + deviceId + channelId;
        resultHolder.put(key, uuid, result);
        StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
        if (streamInfo == null) {
            RequestMessage msg = new RequestMessage();
            msg.setId(uuid);
            msg.setKey(key);
            msg.setData("点播未找到");
            resultHolder.invokeAllResult(msg);
            storager.stopPlay(deviceId, channelId);
            return result;
        }
        cmder.streamByeCmd(deviceId, channelId, streamInfo.getStream(), null, eventResult -> {
            redisCatchStorage.stopPlay(streamInfo);
            storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
            RequestMessage msgForSuccess = new RequestMessage();
            msgForSuccess.setId(uuid);
            msgForSuccess.setKey(key);
            msgForSuccess.setData(String.format("success"));
            resultHolder.invokeAllResult(msgForSuccess);
        });

        if (deviceId != null || channelId != null) {
            JSONObject json = new JSONObject();
            json.put("deviceId", deviceId);
            json.put("channelId", channelId);
            RequestMessage msg = new RequestMessage();
            msg.setId(uuid);
            msg.setKey(key);
            msg.setData(json.toString());
            resultHolder.invokeAllResult(msg);
        } else {
            logger.warn("设备预览/回放停止API调用失败！");
            RequestMessage msg = new RequestMessage();
            msg.setId(uuid);
            msg.setKey(key);
            msg.setData("streamId null");
            resultHolder.invokeAllResult(msg);
        }

        // 超时处理
        result.onTimeout(()->{
            logger.warn(String.format("设备预览/回放停止超时，deviceId/channelId：%s_%s ", deviceId, channelId));
            RequestMessage msg = new RequestMessage();
            msg.setId(uuid);
            msg.setKey(key);
            msg.setData("Timeout");
            resultHolder.invokeAllResult(msg);
        });
        return result;
    }

    /**
     * 将不是h264的视频通过ffmpeg 转码为h264 + aac
     * @param streamId 流ID
     * @return
     */
    @ApiOperation("将不是h264的视频通过ffmpeg 转码为h264 + aac")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "streamId", value = "视频流ID", dataTypeClass = String.class),
    })
    @PostMapping("/convert/{streamId}")
    public ResponseEntity<String> playConvert(@PathVariable String streamId) {
        StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
        if (streamInfo == null) {
            streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null);
        }
        if (streamInfo == null) {
            logger.warn("视频转码API调用失败！, 视频流已经停止!");
            return new ResponseEntity<String>("未找到视频流信息, 视频流可能已经停止", HttpStatus.OK);
        }
        MediaServerItem mediaInfo = mediaServerService.getOne(streamInfo.getMediaServerId());
        JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
        if (!rtpInfo.getBoolean("exist")) {
            logger.warn("视频转码API调用失败！, 视频流已停止推流!");
            return new ResponseEntity<String>("推流信息在流媒体中不存在, 视频流可能已停止推流", HttpStatus.OK);
        } else {
            String dstUrl = String.format("rtmp://%s:%s/convert/%s", "127.0.0.1", mediaInfo.getRtmpPort(),
                    streamId );
            String srcUrl = String.format("rtsp://%s:%s/rtp/%s", "127.0.0.1", mediaInfo.getRtspPort(), streamId);
            JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(mediaInfo, srcUrl, dstUrl, "1000000", true, false, null);
            logger.info(jsonObject.toJSONString());
            JSONObject result = new JSONObject();
            if (jsonObject != null && jsonObject.getInteger("code") == 0) {
                result.put("code", 0);
                JSONObject data = jsonObject.getJSONObject("data");
                if (data != null) {
                    result.put("key", data.getString("key"));
                    StreamInfo streamInfoResult = mediaService.getStreamInfoByAppAndStreamWithCheck("convert", streamId, mediaInfo.getId());
                    result.put("data", streamInfoResult);
                }
            }else {
                result.put("code", 1);
                result.put("msg", "cover fail");
            }
            return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
        }
    }

    /**
     * 结束转码
     * @param key
     * @return
     */
    @ApiOperation("结束转码")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "key", value = "视频流key", dataTypeClass = String.class),
    })
    @PostMapping("/convertStop/{key}")
    public ResponseEntity<String> playConvertStop(@PathVariable String key, String mediaServerId) {
        JSONObject result = new JSONObject();
        if (mediaServerId == null) {
            result.put("code", 400);
            result.put("msg", "mediaServerId is null");
            return new ResponseEntity<String>( result.toJSONString(), HttpStatus.BAD_REQUEST);
        }
        MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
        if (mediaInfo == null) {
            result.put("code", 0);
            result.put("msg", "使用的流媒体已经停止运行");
            return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
        }else {
            JSONObject jsonObject = zlmresTfulUtils.delFFmpegSource(mediaInfo, key);
            logger.info(jsonObject.toJSONString());
            if (jsonObject != null && jsonObject.getInteger("code") == 0) {
                result.put("code", 0);
                JSONObject data = jsonObject.getJSONObject("data");
                if (data != null && data.getBoolean("flag")) {
                    result.put("code", "0");
                    result.put("msg", "success");
                }else {

                }
            }else {
                result.put("code", 1);
                result.put("msg", "delFFmpegSource fail");
            }
            return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
        }


    }

    @ApiOperation("语音广播命令")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "deviceId", value = "设备Id", dataTypeClass = String.class),
    })
    @GetMapping("/broadcast/{deviceId}")
    @PostMapping("/broadcast/{deviceId}")
    public DeferredResult<ResponseEntity<String>> broadcastApi(@PathVariable String deviceId) {
        if (logger.isDebugEnabled()) {
            logger.debug("语音广播API调用");
        }
        Device device = storager.queryVideoDevice(deviceId);
        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
        String key  = DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId;
        if (resultHolder.exist(key, null)) {
            result.setResult(new ResponseEntity<>("设备使用中",HttpStatus.OK));
            return result;
        }
        String uuid  = UUID.randomUUID().toString();
        if (device == null) {

            resultHolder.put(key, key,  result);
            RequestMessage msg = new RequestMessage();
            msg.setKey(key);
            msg.setId(uuid);
            JSONObject json = new JSONObject();
            json.put("DeviceID", deviceId);
            json.put("CmdType", "Broadcast");
            json.put("Result", "Failed");
            json.put("Description", "Device 不存在");
            msg.setData(json);
            resultHolder.invokeResult(msg);
            return result;
        }
        cmder.audioBroadcastCmd(device, (event) -> {
            RequestMessage msg = new RequestMessage();
            msg.setKey(key);
            msg.setId(uuid);
            JSONObject json = new JSONObject();
            json.put("DeviceID", deviceId);
            json.put("CmdType", "Broadcast");
            json.put("Result", "Failed");
            json.put("Description", String.format("语音广播操作失败，错误码： %s, %s", event.statusCode, event.msg));
            msg.setData(json);
            resultHolder.invokeResult(msg);
        });

        result.onTimeout(() -> {
            logger.warn(String.format("语音广播操作超时, 设备未返回应答指令"));
            RequestMessage msg = new RequestMessage();
            msg.setKey(key);
            msg.setId(uuid);
            JSONObject json = new JSONObject();
            json.put("DeviceID", deviceId);
            json.put("CmdType", "Broadcast");
            json.put("Result", "Failed");
            json.put("Error", "Timeout. Device did not response to broadcast command.");
            msg.setData(json);
            resultHolder.invokeResult(msg);
        });
        resultHolder.put(key, uuid, result);
        return result;
    }

    @ApiOperation("获取所有的ssrc")
    @GetMapping("/ssrc")
    public VMCResult<JSONObject> getSSRC() {
        if (logger.isDebugEnabled()) {
            logger.debug("获取所有的ssrc");
        }
        JSONArray objects = new JSONArray();
        List<SsrcTransaction> allSsrc = streamSession.getAllSsrc();
        for (SsrcTransaction transaction : allSsrc) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("deviceId", transaction.getDeviceId());
            jsonObject.put("channelId", transaction.getChannelId());
            jsonObject.put("ssrc", transaction.getSsrc());
            jsonObject.put("streamId", transaction.getStream());
            objects.add(jsonObject);
        }

        VMCResult<JSONObject> result = new VMCResult<>();
        result.setCode(0);
        result.setMsg("success");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("data", objects);
        jsonObject.put("count", objects.size());
        result.setData(jsonObject);
        return result;
    }

}



